package de.dnb.gnd.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.OperationNotSupportedException;
import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import de.dnb.basics.Constants;
import de.dnb.basics.applicationComponents.FileUtils;
import de.dnb.basics.applicationComponents.StringUtils;
import de.dnb.basics.applicationComponents.Pair;
import de.dnb.basics.applicationComponents.RangeCheckUtils;
import de.dnb.basics.applicationComponents.StreamUtils;
import de.dnb.basics.filtering.FilterUtils;
import de.dnb.basics.filtering.IFunction;
import de.dnb.basics.filtering.IPredicate;
import de.dnb.basics.utils.PortalUtils;
import de.dnb.gnd.exceptions.ExceptionHandler;
import de.dnb.gnd.exceptions.IllFormattedLineException;
import de.dnb.gnd.exceptions.WrappingHandler;
import de.dnb.gnd.parser.Field;
import de.dnb.gnd.parser.Format;
import de.dnb.gnd.parser.Indicator;
import de.dnb.gnd.parser.MarcParser;
import de.dnb.gnd.parser.Record;
import de.dnb.gnd.parser.RecordParser;
import de.dnb.gnd.parser.Subfield;
import de.dnb.gnd.parser.line.Line;
import de.dnb.gnd.parser.line.LineParser;
import de.dnb.gnd.parser.tag.BibTagDB;
import de.dnb.gnd.parser.tag.GNDTag;
import de.dnb.gnd.parser.tag.GNDTagDB;
import de.dnb.gnd.parser.tag.Tag;
import de.dnb.gnd.parser.tag.TagDB;

/**
 * Enthält allgemeine Hilfsfunktionen.
 * 
 * @author baumann
 *
 */
public final class RecordUtils {

    public static final TagDB BIB_TAG_DB = BibTagDB.getDB();

    public static final TagDB AUTH_TAG_DB = GNDTagDB.getDB();

    /**
     * ein Zeichen (optional, wird, wenn vorhanden ausgewertet)
     * gefolgt von indestens 7 -11 Zahlen, eventuell gefolgt von x oder X.
     */
    public static final String ID_STR = ".?\\d{7,11}[Xx]?";
    /**
     *  Mindestens 7 -11 Zahlen, eventuell gefolgt von x oder X.
     */
    public static final Pattern PAT_IDN = Pattern.compile(ID_STR);

    /**
     *  ! + Mindestens 7 -11 Zahlen, eventuell gefolgt von x oder X. + !
     */
    public static final String LINK = "!(\\d{7,11}[Xx]?)!";
    /**
     *  Mindestens 7 -11 Zahlen, eventuell gefolgt von x oder X.
     */
    public static final Pattern PAT_LINK = Pattern.compile(LINK);

    //@formatter:off
	
	public static boolean containsLink(String s) {
		Matcher matcher = PAT_LINK.matcher(s);
		return matcher.find();		
	}
	
	/**
	 * Nullsichere idn-Erkennung.
	 * 
	 * @param idn	beleibig
	 * @return		ob idn
	 */
	public static boolean isIDN(final String idn){
		if(idn == null)
			return false;
		Matcher matcher = PAT_IDN.matcher(idn);
		return matcher.matches();
	}
	
	/**
	 * Fügt eine Collection von Zeilen zu record hinzu.
	 * @param record	nicht null.
	 * @param lines		nicht null.
	 * @throws OperationNotSupportedException
	 * 					wenn eine der Zeilen nicht hinzugefügt werden
	 * 					darf.
	 */
	public static
		void
		addLines(final Record record, final Collection<Line> lines)
			throws OperationNotSupportedException {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertReferenceParamNotNull("lines", lines);
		for (final Line line : lines) {
			record.add(line);
		}

	}

	/**
	 * Ist der Tag im Record enthalten?
	 * 
	 * @param record	nicht null.
	 * @param tag		nicht null.
	 * @return			true, wenn tag im record vorkommt.
	 */
	public static boolean containsField(final Record record, final Tag tag) {
		RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
		RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
		final Field field = record.getField(tag);
		return field != null;
	}
	
	/**
	 * Ist der Tag im Record enthalten?
	 * 
	 * @param record	nicht null.
	 * @param tag		nicht null.
	 * @return			true, wenn tag im record vorkommt.
	 */
	public static boolean containsField(final Record record, final String tag) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
		Tag tag2 = record.tagDB.findTag(tag);
		final Field field = record.getField(tag2);
		return field != null;
	}
	/**
	 * Liefert zu einem Datensatz, einem Tag und einem Indikator alle Inhalte
	 * der ersten Unterfelder zu diesem Indikator. Ergebnis kann als Spalte 
	 * einer Tabelle aufgefasst werden.
	 * 
	 * @param record	nicht null.
	 * @param tag		nicht null.
	 * @param indicator	beliebig.
	 *
	 * @return			nicht null.
	 */
	public static List<String> getContentsOfFirstSubfields(
		final Record record,
		final String tag,
		final char indicator) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertStringParamNotNullOrEmpty("tag", tag);
		Field field = getFieldGivenAsString(record, tag);
		if (field == null)
			return Collections.emptyList();

		return SubfieldUtils.getContentsOfFirstSubfields(field, indicator);
	}

	/**
	 * Liefert zu einem Tag und Indikator den Inhalt des Unterfeldes. Zum Tag
	 * muss exakt eine Zeile gehören, sonst wird null geliefert.
	 * 
	 * @param record	nicht null
	 * @param tag		nicht null
	 * @param indicator	beliebig
	 * @return			Inhalt oder null.
	 */
	public static String getContentOfSubfield(
		final Record record,
		final Tag tag,
		final char indicator) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
		Line line = getTheOnlyLine(record, tag);
		if (line == null)
			return null;
		return SubfieldUtils.getContentOfFirstSubfield(line, indicator);
	}

	/**
	 * Liefert zu einem Tag und Indikator den Inhalt des Unterfeldes. Zum Tag
	 * muss exakt eine Zeile gehören, sonst wird null geliefert.
	 * 
	 * @param record	nicht null
	 * @param tagStr		nicht null
	 * @param indicator	beliebig
	 * @return			Inhalt oder null.
	 */
	public static String getContentOfSubfield(
		final Record record,
		String tagStr,
		final char indicator) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertReferenceParamNotNull("tagStr", tagStr);
		Tag tag = record.tagDB.findTag(tagStr);
		return getContentOfSubfield(record, tag, indicator);
	}

	/**
	 * Liefert zu einem Datensatz und einem Tag alle Inhalte der 
	 * im Tag enthaltenen Unterfelder. 
	 * 
	 * @param record	nicht null.
	 * @param tag		nicht null.
	 * @return			nicht null, aber leer, wenn nicht genau eine Zeile zu
	 * 					diesem Tag existiert.
	 */
	public static List<String> getContentsOfSubfields(
		final Record record,
		final String tag) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertStringParamNotNullOrEmpty("tag", tag);
		Line line = getTheOnlyLine(record, tag);
		if (line == null)
			return Collections.emptyList();
		else {
			Collection<Subfield> subfields = line.getSubfields();
			return SubfieldUtils.getContentsOfSubfields(subfields);
		}
	}
	
	/**
	 * Liefert zu einem Datensatz und einem Tag alle Inhalte der 
	 * im Tag enthaltenen Unterfelder, die mit dem Indikator übereinstimmen.
	 * <br/>Zum Tag darf nur eine Zeile gehören.
	 * 
	 * @param record	nicht null.
	 * @param tag		nicht null.
	 * @param ind		beliebig
	 * @return			nicht null, aber leer, wenn nicht genau eine Zeile zu
	 * 					diesem Tag existiert.
	 */
	public static List<String> getContentsOfSubfields(
		final Record record,
		final String tag,
		final char ind) {
		RangeCheckUtils.assertReferenceParamNotNull("record", record);
		RangeCheckUtils.assertStringParamNotNullOrEmpty("tag", tag);
		Line line = getTheOnlyLine(record, tag);
		if (line == null)
			return Collections.emptyList();
		else {			
			return SubfieldUtils.getContentsOfSubfields(line, ind);
		}
	}
	
	//@formatter:on
    /**
     * Liefert zu einem Datensatz und einem Tag alle Inhalte der 
     * im Tag enthaltenen Unterfelder, die mit dem Indikator übereinstimmen.
     * <br/>Zum Tag können auch mehrere Zeilen gehören.
     * 
     * @param record	nicht null.
     * @param tag		nicht null.
     * @param ind		beliebig
     * @return			nicht null, aber eventuell leer.
     */
    public static List<String> getContentsOfAllSubfields(
        final Record record,
        final String tag,
        final char ind) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertStringParamNotNullOrEmpty("tag", tag);
        Collection<Line> lines = getLines(record, tag);

        List<String> strings = new LinkedList<String>();
        for (Line line : lines) {
            strings.addAll(SubfieldUtils.getContentsOfSubfields(line, ind));
        }
        return strings;
    }

    /**
     * Liefert das Feld zu record und aTag.
     * 
     * @param record	nicht null.
     * @param aTag		nicht null, nicht leer, bekannter Tag.
     * @return			Feld oder null, wenn nicht vorhanden.
     */
    public static Field getFieldGivenAsString(
        final Record record,
        final String aTag) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("aTag", aTag);
        final Tag tag = record.tagDB.findTag(aTag);
        RangeCheckUtils.assertReferenceParamNotNull("Tag unbekannt", tag);
        return record.getField(tag);
    }

    /**
     * Liefert a) die erste Zeile einer Collection von Tags im Record 
     * b) die Zahl der  Zeilen.
     * 
     * @param record	nicht null.
     * @param tags		nicht null.
     * @return			Paar aus Zeile und Gesamtzahl der Zeilen.
     * 					Wenn keine Zeile vorhanden, so wird
     * 					(null, 0) zurückgegeben.
     * 
     */
    public static Pair<Line, Integer> getFirstLine(
        final Record record,
        final Collection<Tag> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final List<Line> lines = getLines(record, tags);
        final Integer size = lines.size();
        if (lines.isEmpty()) {
            return new Pair<Line, Integer>(null, size);
        } else {
            return new Pair<Line, Integer>(lines.get(0), size);
        }
    }

    /**
     * Liefert a) die erste Zeile eines Tags im Record b) die Zahl der 
     * Zeilen.
     * 
     * @param record	nicht null.
     * @param tag		nicht null.
     * @return			Paar aus Zeile und Gesamtzahl der Zeilen.
     * 					Wenn keine Zeile vorhanden, so wird
     * 					(null, 0) zurückgegeben.
     * 
     */
    public static Pair<Line, Integer> getFirstLine(
        final Record record,
        final Tag tag) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        final Field field = record.getField(tag);
        if (field == null) {
            return new Pair<Line, Integer>(null, 0);
        }
        final Integer size = field.size();
        final Line line = field.iterator().next();
        return new Pair<Line, Integer>(line, size);
    }

    /**
     * Liefert a) die erste Zeile eines Tags im Record b) die Zahl der 
     * Zeilen.
     * 
     * @param record	nicht null.
     * @param aTag		nicht null.
     * @return			Paar aus Zeile und Gesamtzahl der Zeilen.
     * 					Wenn keine Zeile vorhanden, so wird
     * 					(null, 0) zurückgegeben.
     * 
     */
    public static Pair<Line, Integer> getFirstLineTagGivenAsString(
        final Record record,
        final String aTag) {
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("aTag", aTag);
        final Tag tag = record.tagDB.findTag(aTag);
        return getFirstLine(record, tag);
    }

    //@formatter:on
    /**
     * Liefert Zeile zu einem Tag.
     * 
     * @param record	nicht null.
     * @param aTag		nicht null.
     * @return			Zugehörige Zeile, wenn es die einzige ist, sonst null.
     * 
     */
    public static Line getTheOnlyLine(final Record record, final String aTag) {
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("aTag", aTag);
        final Tag tag = record.tagDB.findTag(aTag);
        return getTheOnlyLine(record, tag);
    }

    /**
     * Liefert Zeile zu einem Tag.
     * 
     * @param record	nicht null.
     * @param tag		nicht null.
     * @return			Zugehörige Zeile, wenn es die einzige ist, sonst null.
     * 
     */
    public static Line getTheOnlyLine(final Record record, final Tag tag) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        Pair<Line, Integer> pair = getFirstLine(record, tag);
        if (pair.second == 1)
            return pair.first;
        else
            return null;
    }

    //@formatter:on
    /**
     * Liefert zu einer Menge von Zeilen und einem Indikator alle ersten
     * Unterfelder zu diesem Indikator. Ergebnis kann als Spalte einer
     * Tabelle aufgefasst werden.
     * 
     * @param lines		nicht null.
     * @param indicator	beliebig.
     *
     * @return			nicht null.
     */
    public static ArrayList<Subfield> getFirstSubfields(
        final Iterable<Line> lines,
        final char indicator) {
        RangeCheckUtils.assertReferenceParamNotNull("lines", lines);
        final IFunction<Line, Subfield> function =
            new IFunction<Line, Subfield>() {
                @Override
                public Subfield apply(final Line line) {
                    return SubfieldUtils.getFirstSubfield(line, indicator);
                }
            };
        return FilterUtils.mapNullFiltered(lines, function);
    }

    /**
     * Liefert die zu den Tags gehörigen Zeilen.
     * 
     * @param record	nicht null
     * @param tags		nicht null.
     * @return			nicht null, modifizierbare Liste.
     */
    public static ArrayList<Line> getLines(
        final Record record,
        final Collection<? extends Tag> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final ArrayList<Line> lines = new ArrayList<Line>();
        for (final Tag tag : tags) {
            final Field field = record.getField(tag);
            if (field != null) {
                lines.addAll(field.getLines());
            }
        }
        return lines;
    }

    /**
     * Liefert alle Zeilen mit Tag aus tags, für die predicate
     * zutrifft.
     * 
     * @param record	nicht null.
     * @param predicate	nicht null.
     * @param tags		nicht null.
     * @return			nicht null, modifizierbar.
     */
    public static ArrayList<Line> getLines(
        final Record record,
        final IPredicate<Line> predicate,
        final Collection<? extends Tag> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("predicate", predicate);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final ArrayList<Line> lines = getLines(record, tags);
        FilterUtils.filter(lines, predicate);
        return lines;
    }

    /**
     * Liefert alle Zeilen mit Tag aus tags, für die predicate
     * zutrifft.
     * 
     * @param record	nicht null.
     * @param predicate	nicht null.
     * @param tags		nicht null.
     * @return			nicht null, modifizierbar.
     */
    public static ArrayList<Line> getLines(
        final Record record,
        final IPredicate<Line> predicate,
        final Tag... tags) {
        return getLines(record, predicate, Arrays.asList(tags));
    }

    /**
     * Liefert die zu den Tags gehörigen Zeilen.
     * 
     * @param record	nicht null
     * @param tags		nicht null.
     * @return			nicht null, , modifizierbar.
     */
    public static ArrayList<Line> getLines(
        final Record record,
        final String... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<String> collection = Arrays.asList(tags);
        return getLinesByTagGivenAsString(record, collection);
    }

    /**
     * Gibt eine Teilmenge der Tags, die zwischen from (einschließlich) und
     * to (einschließlich) liegen.
     * @param record nicht null
     * @param from	nicht null, nicht leer.
     * @param to	nicht null, nicht leer.
     * @return		nicht null, modifizierbar.
     */
    public static Collection<Line> getLinesBetween(
        final Record record,
        final String from,
        final String to) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("from", from);
        RangeCheckUtils.assertStringParamNotNullOrWhitespace("to", to);
        TagDB db = record.tagDB;
        Collection<Tag> tags = db.getTagsBetween(from, to);
        return getLines(record, tags);
    }

    /**
     * Liefert die zu den Tags gehörigen Zeilen.
     * 
     * @param record	nicht null
     * @param tags		nicht null.
     * @return			nicht null, modifizierbar.
     */
    public static ArrayList<Line> getLines(
        final Record record,
        final Tag... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<Tag> collection = Arrays.asList(tags);
        return getLines(record, collection);
    }

    /**
     * Liefert die zu den Tags gehörigen Zeilen.
     * 
     * @param record	nicht null
     * @param tags		nicht null.
     * @return			nicht null, modifizierbar.
     */
    public static ArrayList<Line> getLinesByTagGivenAsString(
        final Record record,
        final Collection<String> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final ArrayList<Line> lines = new ArrayList<Line>();
        for (final String string : tags) {
            final Tag tag = record.tagDB.findTag(string);
            if (tag != null) {
                final Field field = record.getField(tag);
                if (field != null) {
                    lines.addAll(field.getLines());
                }
            }
        }
        return lines;
    }

    /**
     * Liefert alle Zeilen mit Tag aus tags, für die predicate
     * zutrifft.
     * 
     * @param record	nicht null.
     * @param predicate	nicht null.
     * @param tags		nicht null.
     * @return			nicht null, modifizierbar.
     */
    public static ArrayList<Line> getLinesByTagsGivenAsStrings(
        final Record record,
        final IPredicate<Line> predicate,
        final String... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("predicate", predicate);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final ArrayList<Line> lines = getLines(record, tags);
        FilterUtils.filter(lines, predicate);
        return lines;
    }

    /**
     * Gibt eine neue Liste, deren Zeilen predicate erfüllen.
     * @param lines		nicht null
     * @param predicate	nicht null
     * @return			nicht null
     */
    public static List<Line> filter(
        final Iterable<Line> lines,
        final IPredicate<Line> predicate) {
        return FilterUtils.newFilteredList(lines, predicate);
    }

    /**
     * Gibt die relevanten Unterfelder.
     * 
     * @param tag 		nicht null
     * @param subfields nicht null 
     * 
     * @return nicht null, eventuell leer
     */
    public static List<Subfield> getRelevantSubfields(
        Tag tag,
        Iterable<Subfield> subfields) {
        RangeCheckUtils.assertReferenceParamNotNull("subfields", subfields);
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        return SubfieldUtils.removeSubfieldFromCollection(subfields,
            tag.getIgnorableIndicator());
    }

    /**
     * Logt message in die Datei "log/logfileDatensatz.txt".
     * @param message	beliebig.
     */
    public static void logError(final String message) {
        File logfile;
        PrintStream log;

        try {
            final File logfolder = new File("log");
            logfolder.mkdir();

            logfile = new File("log/logfileDatensatz.txt");
            final boolean append = true;

            final FileOutputStream logOS =
                new FileOutputStream(logfile, append);

            log = new PrintStream(logOS);
            log.print(message);
            log.println();
            FileUtils.safeClose(log);
        } catch (final Exception e1) {
            // Gefährlich, aber wohl sinnvoll, da nur ein Logversuch
            // in nicht allzu wichtigen Situationen.
            e1.printStackTrace();
        }

    }

    /**
     * Entfernt $8.
     * 
     * @param subfields	nicht null.
     * @return			gefilterte Collection.
     */
    public static Collection<Subfield> removeExpansion(
        final Collection<Subfield> subfields) {
        RangeCheckUtils.assertReferenceParamNotNull("subfields", subfields);
        return FilterUtils.newFilteredList(subfields, new ExpansionFilter());
    }

    /**
     * Entfernt Zeilen aus dem record.
     * 
     * @param record	nicht null
     * @param lines		nicht null
     */
    public static void removeLines(
        final Record record,
        final Collection<Line> lines) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("lines", lines);
        for (final Line line : lines) {
            record.remove(line);
        }
    }

    /**
     * Entfernt Zeilen aus dem record.
     * 
     * @param record	nicht null
     * @param lines		nicht null
     */
    public static void removeLines(final Record record, final Line... lines) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("lines", lines);
        final Collection<Line> collection = Arrays.asList(lines);
        removeLines(record, collection);
    }

    /**
     * Entfernt tags aus dem record.
     * 
     * @param record	nicht null
     * @param tags		nicht null
     */
    public static void removeTags(
        final Record record,
        final Collection<? extends Tag> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        for (final Tag tag : tags) {
            record.removeField(tag);
        }
    }

    /**
     * Entfernt tags aus dem record.
     * 
     * @param record	nicht null
     * @param tags		nicht null
     */
    public static void removeTags(final Record record, final Tag... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<Tag> collection = Arrays.asList(tags);
        removeTags(record, collection);
    }

    /**
     * Entfernt tags aus dem record.
     * 
     * @param record	nicht null
     * @param tagDB 	nicht null.
     * @param tags		nicht null, Pica3 oder Pica+, als Strings übergeben.
     */
    public static void removeTags(
        final Record record,
        final TagDB tagDB,
        final Collection<String> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        for (final String string : tags) {
            final Tag tag = tagDB.findTag(string);
            record.removeField(tag);
        }
    }

    /**
     * Entfernt tags aus dem record.
     * 
     * @param record	nicht null
     * @param tagDB 	nicht null.
     * @param tags		nicht null, , Pica3 oder Pica+ als String übergeben.
     */
    public static void removeTags(
        final Record record,
        final TagDB tagDB,
        final String... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<String> collection = Arrays.asList(tags);
        removeTags(record, tagDB, collection);
    }

    /**
     * Entfernt alle anderen tags aus dem record.
     * 
     * @param record	nicht null
     * @param tags		nicht null
     */
    public static void retainTags(
        final Record record,
        final Collection<? extends Tag> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        Collection<Tag> tagsOfRecord = record.getTags();
        for (Tag tag : tagsOfRecord) {
            if (!tags.contains(tag))
                record.removeField(tag);
        }
    }

    /**
     * Entfernt alle anderen tags aus dem record.
     * 
     * @param record	nicht null
     * @param tags		nicht null
     */
    public static void retainTags(final Record record, final Tag... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<Tag> collection = Arrays.asList(tags);
        retainTags(record, collection);
    }

    /**
     * Entfernt alle anderen tags aus dem record.
     * 
     * @param record	nicht null
     * @param tagDB 	nicht null.
     * @param tags		nicht null, als Strings übergeben.
     */
    public static void retainTags(
        final Record record,
        final TagDB tagDB,
        final Collection<String> tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        IFunction<String, Tag> function = new IFunction<String, Tag>() {
            @Override
            public Tag apply(final String tag) {
                return tagDB.findTag(tag);
            }
        };
        List<Tag> tagList = FilterUtils.mapNullFiltered(tags, function);
        retainTags(record, tagList);
    }

    /**
     * Entfernt alle anderen tags aus dem record.
     * 
     * @param record	nicht null
     * @param tagDB 	nicht null.
     * @param tags		nicht null, als String übergeben.
     */
    public static void retainTags(
        final Record record,
        final TagDB tagDB,
        final String... tags) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        RangeCheckUtils.assertReferenceParamNotNull("tags", tags);
        final Collection<String> collection = Arrays.asList(tags);
        retainTags(record, tagDB, collection);
    }

    /**
     * Ersetzt eine Zeile eines Datensatzes durch eine andere.
     * 
     * @param record		nicht null
     * @param original		nicht null
     * @param replacement	nicht null
     * @return				true, wenn Änderung erfolgte.
     */
    public static boolean replace(
        final Record record,
        final Line original,
        final Line replacement) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("original", original);
        RangeCheckUtils.assertReferenceParamNotNull("replacement", replacement);
        final Tag tag = original.getTag();
        if (tag != replacement.getTag()) {
            throw new IllegalArgumentException("tags ungleich");
        }
        final Field field = record.getField(tag);
        RangeCheckUtils.assertReferenceParamNotNull("field", field);
        final boolean replaced = field.replace(original, replacement);
        if (replaced) {
            record.setField(field);
        }
        return replaced;
    }

    /**
     * Liefert eine pica3- oder pica+ Repräsentation.
     * 
     * @param lines		nicht null.
     * @param format	pica3 oder pica+
     * @param expanded	true, wenn Expansion der relationierten
     * 					Felder gewünscht ist.
     * @param lineSeparator 
     * 					in der Regel \r\n
     * @param subfieldSeparator 
     * 					in der Regel $
     * @return			nicht null.
     */
    public static String toPica(
        final Collection<Line> lines,
        final Format format,
        final boolean expanded,
        final String lineSeparator,
        final char subfieldSeparator) {
        RangeCheckUtils.assertReferenceParamNotNull("", lines);
        RangeCheckUtils.assertReferenceParamNotNull("", format);
        String s = "";
        for (final Iterator<Line> iterator = lines.iterator(); iterator
            .hasNext();) {
            final Line line = iterator.next();
            s += toPica(line, format, expanded, subfieldSeparator);
            if (iterator.hasNext()) {
                s += lineSeparator;
            }
        }
        return s;
    }

    /**
     * 
     * Wandel eine Zeile in einen String um, wobei null-Werte als
     * "" ausgegeben werden.
     * 
     * @param line		auch null.
     * @param format	pica3 oder pica+
     * @param expanded	mit Expansionen ausgeben.
     * @param subfieldSeparator TODO
     * @return			eine Zeile als String oder ""
     */
    public static String toPica(
        final Line line,
        final Format format,
        final boolean expanded,
        final char subfieldSeparator) {
        if (line == null) {
            return "";
        }
        RangeCheckUtils.assertReferenceParamNotNull("format", format);
        final Collection<Subfield> subfields = line.getSubfields(format);
        return toPica(line.getTag(), subfields, format, expanded,
            subfieldSeparator);
    }

    /**
     * 
     * Wandel den Inhalt einer Zeile in einen String um, wobei null-Werte als
     * "" ausgegeben werden.
     * 
     * @param line		auch null.
     * @param format	pica3 oder pica+
     * @param expanded	mit Expansionen ausgeben.
     * @param subfieldSeparator In der Regel $
     * @return			eine Zeile als String oder ""
     */
    public static String toPicaWithoutTag(
        final Line line,
        final Format format,
        final boolean expanded,
        final char subfieldSeparator) {
        if (line == null) {
            return "";
        }
        RangeCheckUtils.assertReferenceParamNotNull("format", format);
        final Collection<Subfield> subfields = line.getSubfields(format);
        return toPicaWithoutTag(line.getTag(), subfields, format, expanded,
            subfieldSeparator);
    }

    /**
     * 
     * Wandel den Inhalt einer Zeile in einen String um, wobei null-Werte als
     * "" ausgegeben werden.
     * 
     * @param line		auch null.
     *
     * @return		eine Zeile als String (pica3, mit Expansionen, $); 
     * 				oder "", wenn null übergeben worde
     */
    public static String toPicaWithoutTag(final Line line) {
        if (line == null) {
            return "";
        }
        return toPicaWithoutTag(line, Format.PICA3, true, Constants.DOLLAR);

    }

    /**
     * Liefert eine pica3-Repräsentation. 
     * 
     * @param record		nicht null.
     * 
     * @return				pica3, expandiert, lineSeparator = \r\n
     * 						subfieldSeparator = $
     */
    public static String toPica(final Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        return toPica(record, Format.PICA3, true, Constants.LINE_SEPARATOR, '$');
    }

    /**
     * Liefert eine pica3- oder pica+ Repräsentation.
     * 
     * @param record		nicht null.
     * @param format		pica3 oder pica+
     * @param expanded		true, wenn Expansion der relationierten
     * 						Felder gewünscht ist.
     * @param lineSeparator wenn null, dann "\r\n"
     * @param subfieldSeparator 
     * 						wenn '0' dann '$'
     * @return				nicht null.
     */
    public static String toPica(
        final Record record,
        final Format format,
        final boolean expanded,
        String lineSeparator,
        char subfieldSeparator) {
        /*
         * Die Funktion hat eine eigene Implementierung, da der
         * Iterator vom Format abhängt. Daher kann nicht 
         * toPica(Collection, format, ...) verwendet werden!
         */
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        RangeCheckUtils.assertReferenceParamNotNull("format", format);
        if (lineSeparator == null)
            lineSeparator = Constants.LINE_SEPARATOR;
        if (subfieldSeparator == '0')
            subfieldSeparator = Constants.DOLLAR;
        String s = "";
        Iterator<Line> iterator;
        if (format == Format.PICA3) {
            iterator = record.iterator();
        } else {
            iterator = record.picaPlusIterator();
        }

        //@formatter:off
		for (; iterator.hasNext();) {
				//@formatter:on
            final Line line = iterator.next();
            s += toPica(line, format, expanded, subfieldSeparator);
            if (iterator.hasNext()) {
                s += lineSeparator;
            }
        }
        return s.trim();
    }

    /**
     * Liefert eine pica3- oder pica+ Repräsentation. tag muss
     * nicht zu subfields passen!
     * 
     * @param tag		nicht null.
     * @param subfields	nicht null.
     * @param format	pica3 oder pica+
     * @param expanded	true, wenn Expansion der relationierten
     * 					Felder gewünscht ist.
     * @param subfieldSeparator TODO
     * @return			nicht null.
     */
    public static String toPica(
        final Tag tag,
        final Collection<Subfield> subfields,
        final Format format,
        final boolean expanded,
        final char subfieldSeparator) {
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        RangeCheckUtils.assertCollectionParamNotNullOrEmpty("subfields",
            subfields);
        RangeCheckUtils.assertReferenceParamNotNull("format", format);
        String s;
        if (format == Format.PICA3) {
            s = tag.pica3;
        } else {
            s = tag.picaPlus;
        }
        s +=
            " "
                + toPicaWithoutTag(tag, subfields, format, expanded,
                    subfieldSeparator);
        return s;
    }

    /**
     * Liefert eine pica3- oder pica+ Repräsentation. tag muss
     * nicht zu subfields passen!
     * 
     * @param tag		nicht null.
     * @param subfields	nicht null.
     * @param format	pica3 oder pica+
     * @param expanded	true, wenn Expansion der relationierten
     * 					Felder gewünscht ist.
     * @param subfieldSeparator 
     * 					Unterfeldtrenner, in der Regel '$' bei Pica3
     * @return			nicht null.
     */
    public static String toPicaWithoutTag(
        final Tag tag,
        final Collection<Subfield> subfields,
        final Format format,
        final boolean expanded,
        final char subfieldSeparator) {
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        RangeCheckUtils.assertCollectionParamNotNullOrEmpty("subfields",
            subfields);
        RangeCheckUtils.assertReferenceParamNotNull("format", format);

        String subfieldSeparatorStr = Character.toString(subfieldSeparator);

        Collection<Subfield> adjusted;
        if (expanded) {
            adjusted = subfields;
        } else {
            adjusted = removeExpansion(subfields);
        }
        if (format == Format.PICA3)
            adjusted = getRelevantSubfields(tag, adjusted);
        boolean isFirst = true;
        boolean isFirstAttaching = true;
        String s = "";
        for (final Subfield subfield : adjusted) {
            final Indicator indicator = subfield.getIndicator();
            final char indChar = indicator.indicatorChar;
            String content = subfield.getContent();

            // für "k p" wieder Dollars maskieren:
            if (format == Format.PICA_PLUS
                && subfieldSeparator == Constants.DOLLAR)
                content = content.replace("$", "$$");

            if (format == Format.PICA_PLUS) {
                s += subfieldSeparatorStr + indChar + content;
            } else { // pica3
                if (isFirst && indicator == tag.getDefaultFirst()) {
                    s += content;
                } else if (indicator.prefix != null) {
                    String prefix;
                    // Alternatives Präfix wird benutzt,
                    // wenn Indikator nicht an erster Stelle und
                    // nicht anschliessend ist:
                    if (!isFirst && indicator.prefixAlt != null
                        && !indicator.isAttaching)
                        prefix = indicator.prefixAlt;
                    else if (isFirstAttaching && indicator.prefixAlt != null) {
                        prefix = indicator.prefix;
                        isFirstAttaching = false;
                    } else if (!isFirstAttaching && indicator.prefixAlt != null) {
                        prefix = indicator.prefixAlt;
                    } else
                        prefix = indicator.prefix;
                    // postfix != null
                    s += prefix + content + indicator.postfix;
                } else {
                    // Standardverhalten für GND:
                    s += subfieldSeparatorStr + indChar + content;
                }
                isFirst = false;
            }
        }
        return s;

    }

    /**
     * Liefert eine pica3- oder pica+ Repräsentation. tag muss
     * nicht zu subfields passen!
     * 
     * @param tag		nicht null.
     * @param subfields	nicht null.
     * @return			nicht null.
     */
    public static String toPicaWithoutTag(
        final Tag tag,
        final Collection<Subfield> subfields) {
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        RangeCheckUtils.assertCollectionParamNotNullOrEmpty("subfields",
            subfields);
        return toPicaWithoutTag(tag, subfields, Format.PICA3, true, '$');
    }

    /**
     * Ausgabe im pica3-Format, expandiert, lineSeparator = \r\n,
     * subfieldSeparator = $.
     * 
     * @param lines	nicht null.
     * @return		nicht null.
     */
    public static String toPicaExpanded(final Collection<Line> lines) {
        RangeCheckUtils.assertReferenceParamNotNull("lines", lines);
        return toPica(lines, Format.PICA3, true, Constants.LINE_SEPARATOR, '$');
    }

    /**
     * Hilft beim Aufbau von Zeilen. In eine vorgegebene Liste werden
     * die Unterfelder aus from an der passenden Position eingefügt.
     * Die Standardreihenfolge der Indikatoren wird von tag vorgegeben.
     * Ist schon ein Unterfeld zu gleichem Indikator vorhanden, so wird
     * nach diesem eingefügt.
     * 
     * 
     * @param from		einzufügende Liste, nicht null
     * @param to		Liste, in die eingefügt werden soll.
     * @param tag			nicht null
     */
    public static void insertAtBestPosition(
        List<Subfield> from,
        List<Subfield> to,
        Tag tag) {

        RangeCheckUtils.assertReferenceParamNotNull("from", from);
        RangeCheckUtils.assertReferenceParamNotNull("to", to);
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        for (Subfield subfield : from) {
            insertAtBestPosition(subfield, to, tag);
        }
    }

    /**
     * Hilft beim Aufbau von Zeilen. In eine vorgegebene Liste wird
     * das Unterfeld subfield an der passenden Position eingefügt.
     * Die Standardreihenfolge der Indikatoren wird von tag vorgegeben.
     * Ist schon ein Unterfeld zu gleichem Indikator vorhanden, so wird
     * nach diesem eingefügt.
     * @param subfield		einzufügendes Unterfeld, nicht null
     * @param subfieldList	Liste, in die eingefügt werden soll.
     * @param tag			nicht null
     */
    public static void insertAtBestPosition(
        Subfield subfield,
        List<Subfield> subfieldList,
        Tag tag) {

        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        RangeCheckUtils.assertReferenceParamNotNull("subfield", subfield);
        RangeCheckUtils.assertReferenceParamNotNull("subfieldList",
            subfieldList);
        Character indicator =
            SubfieldUtils.FUNCTION_SUBFIELD_TO_INDICATOR_CHAR.apply(subfield);
        List<Indicator> inds =
            new LinkedList<Indicator>(tag.getAllIndicators());

        // Eine Standardreihenfolge aller Indikatoren herstellen:
        List<Character> allIndicators =
            FilterUtils.map(inds, SubfieldUtils.FUNCTION_INDICATOR_TO_CHAR);
        // Nur Indikatoren, die bis indicator liegen, berücksichtigen.
        int indPos = allIndicators.lastIndexOf(indicator);
        allIndicators = allIndicators.subList(0, indPos + 1);
        // Liste der gegebenen Indikatoren:
        List<Character> givenIndicators =
            FilterUtils.map(subfieldList,
                SubfieldUtils.FUNCTION_SUBFIELD_TO_INDICATOR_CHAR);
        /*
         * Sinnvollste Position erraten:
         * Für jeden Indikator der Standardreihenfolge die letzte
         * Position finden.
         */
        int pos = -1;
        for (Character ind : allIndicators) {
            pos = Math.max(pos, givenIndicators.lastIndexOf(ind));
        }
        subfieldList.add(pos + 1, subfield);
    }

    /**
     * Gibt eine normierte, nicht regelgerechte Darstellung einer
     * Zeile.
     * @param line	nicht null.
     * @return		nicht null.
     */
    public static String toString(final Line line) {
        RangeCheckUtils.assertReferenceParamNotNull("line", line);
        return toString(line.getTag(), line.getSubfields(Format.PICA3));
    }

    /**
     * Gibt eine normierte, nicht regelgerechte Darstellung einer
     * Zeile, bestehend aus tag und subfields. tag muss nicht zu
     * subfields passen!
     * 
     * @param tag		nicht null.
     * @param subfields	nicht null, nicht leer
     * @return			nicht null.
     */
    public static String toString(
        final Tag tag,
        final Collection<Subfield> subfields) {
        RangeCheckUtils.assertReferenceParamNotNull("tag", tag);
        RangeCheckUtils.assertCollectionParamNotNullOrEmpty("subfields",
            subfields);
        String s = tag.pica3 + " ";
        for (final Subfield subfield : subfields) {
            s += subfield;
        }
        return s;
    }

    /**
     * Liest einen Datensatz von der Standardeingabe. Fehler werden
     * ignoriert.
     * @param tagDB	nicht null
     * @return		neuen Datensatz.
     */
    public static Record readFromConsole(final TagDB tagDB) {
        return readFromConsole(tagDB, null);
    }

    /**
     * Liest die xml-Darstellung vom Portal und wandelt sie, so gut es geht,
     * in einen Datensatz um.
     * 
     * @param idn	nicht null
     * @return		Datensatz oder null
     */
    public static Record readFromPortal(final String idn) {
        RangeCheckUtils.assertReferenceParamNotNull("", idn);
        org.marc4j.marc.Record marcRecord = PortalUtils.getMarcRecord(idn);
        if (marcRecord == null)
            return null;
        MarcParser parser = new MarcParser();
        Record record = parser.parse(marcRecord);
        return record;
    }

    /**
     * Liest einen Datensatz von der Standardeingabe.
     * @param tagDB	nicht null
     * @param handler	beliebig
     * @return		neuen Datensatz.
     */
    public static Record readFromConsole(
        final TagDB tagDB,
        final ExceptionHandler handler) {
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        System.out.println("Bitte Datensatz eingeben (beenden mit STRG-Z):");
        String string = StreamUtils.readIntoString(System.in);
        RecordParser parser = new RecordParser();
        parser.setHandler(handler);
        parser.setDefaultTagDB(tagDB);
        return parser.parse(string);
    }

    /**
     * Liest einen Datensatz aus der Zwischenablage. Die nötige Datenbank
     * wird aus dem Datensatz ermittelt.
     * 
     * @return	neuen Datensatz oder null, wenn Clipboard nicht in String
     * konvertierbar.
     */
    public static Record readFromClip() {
        String string = StringUtils.readClipboard();
        if (string == null)
            return null;
        RecordParser parser = new RecordParser();
        return parser.parse(string);
    }

    /**
     * Liest einen Datensatz aus der Zwischenablage.
     * @param tagDB			nicht null
     * @param handler		beliebig
     * @param ignoreMARC 	Überlese beim Parsen von Pica+-Daten die Felder, 
     * 						die in MARC21 üblich sind, die aber nicht zum 
     * 						Pica-Format gehören.
     * @return				neuen Datensatz oder null, wenn Clipboard nicht 
     * 						in String konvertierbar.
     */
    public static Record readFromClip(
        final TagDB tagDB,
        final ExceptionHandler handler,
        boolean ignoreMARC) {
        RangeCheckUtils.assertReferenceParamNotNull("tagDB", tagDB);
        String string = StringUtils.readClipboard();
        if (string == null)
            return null;
        RecordParser parser = new RecordParser();
        parser.setHandler(handler);
        parser.setDefaultTagDB(tagDB);
        parser.setIgnoreMARC(ignoreMARC);
        return parser.parse(string);
    }

    /**
     * Liest einen Datensatz aus der Zwischenablage. Fehler werden
     * ignoriert.
     * @param tagDB	nicht null
     * @return		neuen Datensatz oder null, wenn Clipboard nicht 
     * 				in String konvertierbar.
     */
    public static Record readFromClip(final TagDB tagDB) {
        return readFromClip(tagDB, null, false);
    }

    /**
     * 
     * @param record	beliebig
     * @return			true, wenn null oder ohne Felder
     */
    public static boolean isNullOrEmpty(final Record record) {
        return (record == null || record.getFields().isEmpty());
    }

    public static boolean isAuthority(Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        return record.tagDB == AUTH_TAG_DB;
    }

    public static boolean isBibliographic(Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        return record.tagDB == BIB_TAG_DB;
    }

    /**
     * Liefert die Satzart. Etwa Tp1 aus 005 bei Normdaten oder 
     * "Abvz" aus 0500 bei Titeldaten.
     * @param record	nicht null
     * @return			Titeldaten oder leer, wenn nicht gefunden.
     */
    public static String getDatatype(final Record record) {
        Line line = getTheOnlyLine(record, "002@");
        if (line == null)
            return "";
        return SubfieldUtils.getContentOfFirstSubfield(line, '0');
    }

    /**
     * Liefert das Zeichen an Stelle index (beginnend bei 0) der Satzart. 
     * 
     * @param record	nicht null
     * @param index 	beliebig.
     * @return			Zeichen. 0, wenn nicht gefunden oder index zu gross
     * 					oder zu klein.
     */
    public static char getDatatypeCharacterAt(
        final Record record,
        final int index) {
        String type = RecordUtils.getDatatype(record);
        return StringUtils.charAt(type, index);
    }

    /**
     * Gibt die PICA-Idn.
     * 
     * @param record 	nicht null
     * @return			Nummer oder null
     */
    public static String getIDN(final Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        return record.getId();
    }

    /**
     * Nimmt die DB von record und entfernt die von der SE nicht zu
     * modifizierenden Felder.
     * 
     * @param record	nicht null.
     */
    public static void removeUnmodifiables(Record record) {
        RangeCheckUtils.assertReferenceParamNotNull("record", record);
        Collection<Tag> tags = record.tagDB.getUnmodifiables();
        RecordUtils.removeTags(record, tags);
    }

    //@formatter:on
    private RecordUtils() {
        super();
        // TODO Auto-generated constructor stub
    }

    public static final String PAT_PICA_STR_AUTH = "^005 T";

    public static final Pattern PAT_PICA_AUTH = Pattern.compile(
        PAT_PICA_STR_AUTH, Pattern.MULTILINE);

    public static final String PAT_PICA_P_STR_AUTH = "002@ .0T";

    public static final Pattern PAT_PICA_P_AUTH = Pattern
        .compile(PAT_PICA_P_STR_AUTH);

    public static final String PAT_TYPE_STR = "^005 |^0500 ";

    public static final Pattern PAT_TYPE_PICA = Pattern.compile(PAT_TYPE_STR,
        Pattern.MULTILINE);

    public static final String PAT_TYPE_PICA_STR = "002@ .0";

    public static final Pattern PAT_TYPE_PICA_P = Pattern
        .compile(PAT_TYPE_PICA_STR);

    /**
     * Schlichte, zum Parsen benützte Methode.
     * 
     * @param recStr	nicht null
     * @return			ob Datensatz Normdatensatz ist.
     */
    public static boolean isAuthority(final CharSequence recStr) {
        RangeCheckUtils.assertReferenceParamNotNull("recStr", recStr);
        Matcher matcher = PAT_PICA_AUTH.matcher(recStr);
        if (matcher.find())
            return true;
        matcher = PAT_PICA_P_AUTH.matcher(recStr);
        if (matcher.find())
            return true;
        return false;
    }

    /**
     * Schlichte, zum Parsen benützte Methode.
     * 
     * @param recStr	nicht null
     * @return			ob aus dem Datensatz der Datensatztyp ermittelbar ist.
     */
    public static boolean containsRecordType(final CharSequence recStr) {
        Matcher matcher = PAT_TYPE_PICA.matcher(recStr);
        if (matcher.find())
            return true;
        matcher = PAT_TYPE_PICA_P.matcher(recStr);
        if (matcher.find())
            return true;
        return false;
    }

    /**
     * @param args
     * @throws IllFormattedLineException 
     * @throws OperationNotSupportedException 
     * @throws IOException 
     */
    public static void main2(final String[] args)
        throws IllFormattedLineException,
        OperationNotSupportedException,
        IOException {
        Line line =
            LineParser.parse(
                "4000 Von der Physik zur Ph$ilosophie / Jörg Fidorra",
                BibTagDB.getDB(), false);
        System.out.println(line);
        System.out.println(toPica(line, Format.PICA_PLUS, true,
            Constants.DOLLAR));
    }

    public static final IFunction<Line, String> LINE_TO_STRING =
        new IFunction<Line, String>() {
            @Override
            public String apply(Line x) {
                if (x == null)
                    return null;
                return x.getTag().pica3;
            }
        };

    /**
     * Liefert zu einer Menge von Zeilen die Pica3-Tags.
     * 
     * @param lines nicht null
     * @return      nicht null, enthält keine null
     */
    public static Collection<String> getTags(final Collection<Line> lines) {
        return FilterUtils.mapNullFiltered(lines, LINE_TO_STRING);
    }

    /**
     * @param args
     * @throws IllFormattedLineException 
     * @throws OperationNotSupportedException 
     * @throws IOException 
     */
    public static void main(final String[] args)
        throws IllFormattedLineException,
        OperationNotSupportedException,
        IOException {
        Record record = readFromClip();
        Collection<Line> list = SubjectUtils.getDDCSegment(record);
        System.out.println(getTags(list));
    }

}
